package fission;

import java.util.ArrayList;

/**
 * This class functions as an AI agent for the game Fission 
 * It uses Iterative Deepening, Minimax and Alpha-Beta Pruning 
 * algorithms in order to best play the game.
 * @author Leslie and Judith
 */
public class BetterPlayer extends FissionPlayer {

        /**
         * This is a constructor which creates an instance of BetterPlayer
         * by calling it's parent class FissionPlayer's constructor.
         * @param name The name of the player
         * @param moveType Whether the player plays as horizontal or vertical
         */
	public BetterPlayer(String name, boolean moveType) {
		super(name, moveType);
	}
        
        /**
         * This function prints out comments to taunt the opponent
         * @return The comment to be made
         */
	static final String[] COMMENTS = {
		"My static evaluation is far superior!",
		"What kind of move is that?!",
		"Half an hour later...",
		"-sigh- Such a boring attempt.",
		"My knowledge is far greater than your own!",
		"Do you think you can beat me with moves like that?!",
		"EXTERMINATE",
		"Since when were you under the delusion that you could beat me?",
                "Brute force FTW"
	};

        /**
         * This method creates a copy of the game board so that future 
         * outcomes can be calculated without altering the original game.
         * @param g The game to be copied
         * @return The copied game board
         */
	private boolean[][] boardCopy(FissionGame g) {
		boolean[][] board = new boolean[g.getColumns()][g.getRows()];
		for (int r = 0; r < g.getRows(); r++) {
			for (int c = 0; c < g.getColumns(); c++) {
				board[r][c] = g.getBoard()[r][c];
			}
		}
		return board;
	}


        /**
         * This method calculates the next move the AI agent must make. 
         * @param g The game to be observed
         * @throws InterruptedException Indicates when the turn is over
         */
	@Override
	public void generateMove(FissionGame g) throws InterruptedException{
                ArrayList<FissionMove> possibleMoves = generateChildren(g, getType());
		if (possibleMoves.size()  >0) {
			move = possibleMoves.get(0);
		}else{
			return;
		}
		int bestMoveEval = Integer.MIN_VALUE;
		for(int depth = 1;!Thread.currentThread().isInterrupted();depth+=1){
			for (FissionMove possibleMove : possibleMoves) {
				if(Thread.currentThread().isInterrupted()){
					break;
				}
				FissionGame game = new FissionGame(boardCopy(g));
				game.doMove(possibleMove);
				int alphaBeta = alphaBeta(game, depth, bestMoveEval, Integer.MAX_VALUE, !getType());
				//int alphaBeta = alphaBeta(game, depth, possibleMove, !getType());
				if (alphaBeta > bestMoveEval) {
					bestMoveEval = alphaBeta;
					move = possibleMove;
				}
			}
		}
		throw new InterruptedException();
	}

        /**
         * Returns the maximum of two numbers
         * @param one The first number
         * @param two The second number
         * @return the maximum number
         */
	private int max(int one, int two) {
		return one > two ? one : two;
	}

        /**
         * Returns the minimum of two numbers
         * @param one The first number
         * @param two The second number
         * @return the minimum number
         */
	private int min(int one, int two) {
		return one < two ? one : two;
	}
        
        /**
         * This is the Minimax algorithm with the alpha-beta pruning.
         * (See more in written notes)
         * @param node The game we are observing
         * @param depth The depth of the game tree we are searching to
         * @param alpha The value of the best move for the AI agent
         * @param beta The value of the best move for the opponent 
         * @param playerType Whether the player plays Vertical or Horizontal
         * @return The best move value
         */
	private int alphaBeta(FissionGame node, int depth, int alpha, int beta, boolean playerType) {
		if(Thread.currentThread().isInterrupted()){
			if(playerType ==Fission.VERTICAL)
				return alpha;
			else
				return beta;
		}
		if (depth <= 0) {
			return getEvaluation(node);
		}
		ArrayList<FissionMove> possibleMoves = new ArrayList<FissionMove>();
		for (int r = 0; r < node.getRows(); r++) {
			for (int c = 0; c < node.getColumns(); c++) {
				FissionMove m = new FissionMove(r, c, playerType);
				if (node.isValidMove(m)) {
					possibleMoves.add(m);
				}
			}
		}
		if (possibleMoves.size() <= 0) {
			return getEvaluation(node);
		}
		if (playerType == Fission.VERTICAL) {//max
			for (FissionMove m : possibleMoves) {
				FissionGame child = new FissionGame(boardCopy(node));
				child.doMove(m);
				alpha = max(alpha, alphaBeta(child, depth - 1, alpha, beta, !playerType));
				if (beta <= alpha) {
					break;
				}
			}
			return alpha;
		} else {//min
			for (FissionMove m : possibleMoves) {
				FissionGame child = new FissionGame(node.getBoard());
				child.doMove(m);
				beta = min(beta, alphaBeta(child, depth - 1, alpha, beta, !playerType));
				if (beta <= alpha) {
					break;
				}
			}
			return beta;
		}
	}

        /**
         * This method generates a random comment
         * @return the random comment to be made
         */
	@Override
	public String makeComment() {
		return COMMENTS[(int) (Math.random() * COMMENTS.length)];
	}

        /**
         * This method evaluates the game board and returns a score.
         * (See written notes)
         * @param game The game to be evaluated
         * @return The score of the game
         */
	public int getEvaluation(FissionGame game) {
		int score = 0;
		int otherPlayerMoves=0,ourMoves=0;
		boolean board[][] = game.getBoard();
		for (int r = 0; r < game.getRows(); r++) {
			int s = 0; 
			int end = game.getColumns();
			if (r == 0|| r == game.getRows() - 1) { //skips out on corners
				s = 1;
				end--;
			} 
			for (int c = s; c < end; c++) {
				if (board[r][c] == Fission.STONE) { //if we find a stone
					if (r != 0 && r != game.getRows() - 1
							&& board[r - 1][c] == Fission.SPACE
							&& board[r + 1][c] == Fission.SPACE) {
						if (getType() == Fission.VERTICAL) {
							score++;
							ourMoves++;
						} else {
							score--;
							otherPlayerMoves++;
						}
					}
					if (c != 0 && c != game.getColumns() - 1
							&& board[r][c - 1] == Fission.SPACE
							&& board[r][c + 1] == Fission.SPACE) {
						if (getType() == Fission.VERTICAL) {
							score--;
							otherPlayerMoves++;
						} else {
							score++;
							ourMoves++;
						}
					}
				}
			}
		}
		if(ourMoves == 0){
			return Integer.MIN_VALUE;//very very bad
		}
		if(otherPlayerMoves ==0){//very very good move
			return Integer.MAX_VALUE;
		}
		return score;
	}
        
        /**
         * This method generates a set of next moves on the game tree
         * @param node The parent game node
         * @param playerType Whether it is a Vertical or Horizontal move
         * @return An array list of the set of next moves
         */
	private ArrayList<FissionMove> generateChildren(FissionGame node,boolean playerType){
		ArrayList<FissionMove> possibleMoves = new ArrayList<FissionMove>();
		for(int r = 0; r < node.getRows(); r++) {
			for(int c = 0; c < node.getColumns(); c++) {
				FissionMove m = new FissionMove(r, c, playerType);
				if (node.isValidMove(m)) {
					possibleMoves.add(m);
				}
			}
		}
		return possibleMoves;
	}
}
